LINUX信号处理(sigaction信号捕获函数:struct sigaction)

您所在的位置:网站首页 memset 在哪个头文件 LINUX信号处理(sigaction信号捕获函数:struct sigaction)

LINUX信号处理(sigaction信号捕获函数:struct sigaction)

2023-08-22 11:16| 来源: 网络整理| 查看: 265

信号概述

何为信号:信号就是由用户、系统或进程发送给目标进程的信息,以通知目标进程中某个状态的改变或是异常。

信号产生:总体来说,其产生的条件有两种,分别是:硬件和软件原因,又称为:硬中断和软中断。可细分为如下几种原因:

①系统终端Terminal中输入特殊的字符来产生一个信号,比如按下:ctrl+\会产生SIGQUIT信号。

②系统异常。比如访问非法内存和浮点数异常。

③系统状态变化。如设置了alarm定时器,当该定时器到期时候会引起SIGVTALRM信号。

④调用了kill命令或是kill函数。

include include include include using namespace std; void sig_handler(int signum) {     if(0 > signum) { fprintf(stderr,"sig_handler param err. [%d]\n",signum); return; } if(SIGINT == signum) { printf("Received signal [%s]\n",SIGINTsignum?"SIGINT":"Other"); } if(SIGQUIT == signum) { printf("Received signal [%s]\n",SIGQUITsignum?"SIGQUIT":"Other"); } return; } int main(int argc,char **argv) { printf("Wait for the signal to arrive.\n "); /*登记信息*/ signal(SIGINT,sig_handler); signal(SIGQUIT,sig_handler); pause(); pause(); signal(SIGINT,SIG_IGN); return 0; }

程序运行后会一直等待用户的输入,当在终端按下ctrl+c时候会打印 Received signal [SIGINT] 说明捕获到了SIGINT信号,接着程序继续等待,当按下ctrl+\时候会打印Received signal [SIGQUIT],表明捕获到了SIGQUIT信号:

头文件

头文件为处理各种各样的信号声明了一个类型和两个函数,并且定义了几个宏。

1、类型:sig_atomic_t

sig_atomic_t是 int 类型,在信号处理程序中作为变量使用。它是一个对象的整数类型,该对象可以作为一个原子实体访问,即使存在异步信号时,该对象可以作为一个原子实体访。

2、宏:

SIG_DFL  默认的信号处理。

SIG_ERR  表示一个信号错误。

SIG_IGN  表示忽略信号。

有效的信号包括:

SIGABRT  异常终止,如调用abort()。

SIGFPE  算术运算出错,如除数为0或溢出。

SIGILL  非法函数映象,如非法指令。

SIGINT  交互式信号,如中断。

SIGSEGV  非法访问存储器,如访问不存在的内存单元。

SIGTERM  发送给本程序的终止请求信号。

 

一、函数结构

#include

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

参数:

参数1:要捕获的信号参数2:接收到信号之后对信号进行处理的结构体参数3:接收到信号之后,保存原来对此信号处理的各种方式与信号(可用来做备份)。如果不需要备份,此处可以填NULL

返回值:

成功时:返回0出错时:返回-1,并将errno设置为指示错误

与signal的不同,有哪些新功能?

signal只能捕获信号,对信号进行处理。但是不能获取信号的其它信息sigaction可以使用sigaction结构体的sa_handler函数对信号进行处理(此处等同于signal函数),也可以使用sa_sigaction函数查看信号的各种详细信息并且sigaction函数还可以通过sa_mask、sa_flags对信号处理时进行很多其他操作 二、struct sigaction结构体

struct sigaction {

void (*sa_handler)(int);

void (*sa_sigaction)(int, siginfo_t *, void *);

 

sigset_t sa_mask;

int sa_flags;

 

void (*sa_restorer)(void);

};

sa_handler成员: 对捕获的信号进行处理的函数,函数参数为sigaction函数的参数1信号(概念上等同于单独使用signal函数)也可以设置为后面两个常量:常数SIG_IGN(向内核表示忽略此信号)或是常数SIG_DFL(表示接到此信号后的动作是系统默认动作) sa_mask成员: 功能:sa_mask是一个信号集,当接收到某个信号,并且调用sa_handler函数对信号处理之前,把该信号集里面的信号加入到进程的信号屏蔽字当中,当sa_handler函数执行完之后,这个信号集中的信号又会从进程的信号屏蔽字中移除为什么这样设计??这样保证了当正在处理一个信号时,如果此种信号再次发生,信号就会阻塞。如果阻塞期间产生了多个同种类型的信号,那么当sa_handler处理完之后。进程又只接受一个这种信号即使没有信号需要屏蔽,也要初始化这个成员(sigemptyset()),不能保证sa_mask=0会做同样的事情sigset_t数据类型见文章:https://blog.csdn.net/qq_41453285/article/details/89228297 sa_restorer成员: 已经被抛弃了,不再使用 sa_flags成员: 指定了对信号进行哪些特殊的处理 SA_INTERRUPT

由此信号中断的系统调用不自动重启动

SA_NOCLDSTOP

若signo是SIGCHLD:

当一子进程停止(暂停时)时(作业控制), 不产生此信号

当一子进程终止时,仍旧产生此信号

若已设置此标志,则当停止的进程继续运行时,作为XSI扩展,不产生SIGCHLD信号

(参照的下面的SA_NOCLDWAIT选项)

SA_NOCLDWAIT

若signo是SIGCHLD,则当调用进程的子进程终止时, 不创建僵死进程

若调用进程在后面调用wait,则阻塞到它所有子进程都终止,此时返回-1,errno设置为ECHILD

SA_NODEFER

当捕捉到此信号时,在执行其信号捕捉函数时,系统不自动阻塞此信号(除非sa_mask成员包括了此信号)

注意,此种类型的操作对应于早期的不可靠信号

SA_NOMASK同SA_NODEFERSA_ONSTACK若用sigaltstack已声明了一替换栈,则此信号递送给替换栈上的进程SA_STACK同SA_ONSTACKSA_RESETHAND

在此信号捕捉函数的入口处,将此信号的处理方式重置为SIG_DFL,并清除SA_SIGINFO标志

注意,此种类型的信号对应于早期的不可靠信号。但是不能重置SIGILL和SIGTRAP信号的配置

设置此标志使sigaction的行为如同设置了SA_NODEFER标志

SA_ONESHOT同SA_RESETHANDSA_RESTART由此信号中断的系统调用自动重启动SA_SIGINFO此选项对信号处理程序提供了附加信息:一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针 关于SA_INTERRUPT、SA_RESTART说明: 某些早期系统(如SunOS)定义了SA_INTERRUPT标志,这些系统的默认方式是重新启动被中断的系统调用,而制定此标志则使系统调用被中断后不再重新启动。Linux定义SA_INTERRUPT标志,以便与使用该标志的应用程序兼容。但是,如若信号处理程序是用sigaction设置的,那么其默认方式是不重新启动系统调用Single UNIX Specifiaction的XSI扩展规定,除非说明了SA_RESTART标志,否则sigaction函数不再重启动被中断的系统调用关于系统调用中断请参阅:https://blog.csdn.net/qq_41453285/article/details/89216990 sa_sigaction成员: 当sa_flags成员是SA_SIGINFO标志时,就调用此函数,可以来获取该信号的很多详细信息(而不是用来对信号进行处理)参数1:参数为sigaction函数的参数1信号参数2:一个结构体包,含了信号产生的各种详细信息 si_signo:信号的值si_code:信号代码si_pid:信号来自于哪一个进程(哪个进程发送来的)si_uid:信号来自于哪个用户(发送来的进程的用户)si_value(重点):传递的信息。应用程序在传递信号时,可以在si_value.sival_int传递一个整型或者si_value.sival_ptr传递一块内存(下面会有一个案例)

若信号时SIGCHLD:则将设置si_pid、si_status、si_uid若信号时SIGBUS、SIGILL、SIGFPE、SIGSEGV:则si_addr包含造成故障的根源地址(该地址可能并不正确)  

siginfo_t {

int si_signo; /* Signal number */

int si_errno; /* An errno value */

int si_code; /* Signal code */

int si_trapno; /* Trap number that caused

hardware-generated signal(unused on most architectures) */

pid_t si_pid; /* Sending process ID */

uid_t si_uid; /* Real user ID of sending process */

int si_status; /* Exit value or signal */

clock_t si_utime; /* User time consumed */

clock_t si_stime; /* System time consumed */

sigval_t si_value; /* Signal value */

int si_int; /* POSIX.1b signal */

void *si_ptr; /* POSIX.1b signal */

int si_overrun; /* Timer overrun count; POSIX.1b timers */

int si_timerid; /* Timer ID; POSIX.1b timers */

void *si_addr; /* Memory location which caused fault */

long si_band; /* Band event (was int inglibc 2.3.2 and earlier) */

int si_fd; /* File descriptor */

short si_addr_lsb; /* Least significant bit of address(since Linux 2.6.32) */

}

 参数3:此参数为void*类型,可以自己传入一些参数进去,但是一般强转为ucontext_t结构体类型使用,该结构体标识信号传递时进程的上下文。下面是ucontext_t结构体内的一些部分成员  

ucontext_t *uc_link; /* pointer to context resumed when this context returns */

sigset_t uc_sigmask; /* signals blocked when this context is active */

stack_t uc_stack; /* stack used by this context */

mcontext_t uc_mcontext; /* machine-specific representation of saved context */

 

//uc_stack字段描述了当前上下面使用的栈,至少包括下列成员

void *ss_sp; /* stack base or pointer */

size_t ss_size; /* stack size */

int ss_flags; /* flags */

sa_handler和sa_sigaction函数一次只能使用其中一个 如果只是想处理信号,但是不获取信号信息,就使用sa_handler函数如果想要处理信号,并且获取信号的各种信息,就使用sa_sigaction函数

三、演示案例 案例一:处理SIGQUIT信号 

#include

#include

#include

void myHandler(int sig);

int main(int argc,char *argv[])

{

struct sigaction act, oact;

 

act.sa_handler = myHandler;

sigemptyset(&act.sa_mask);

act.sa_flags = 0;

sigaction(SIGQUIT, &act, &oact);

while (1)

{

printf("running...\n");

pause();

}

}

void myHandler(int sig)

{

printf("I got signal: %d.\n", sig);

}

按下ctrl+\之后,就会调用myHandler函数 

案例二:接到SIGUSR1信号时,获取其信息

#include

#include

#include

#include

 

void func(int signo, siginfo_t *info, void *p)

{

printf("signo=%d\n",signo);

printf("sender sigal pid=%d\n",info->si_pid);

}

 

int main(int argc,char *argv[])

{

struct sigaction act, oact;

 

sigemptyset(&act.sa_mask);

act.sa_flags = SA_SIGINFO; //设置此项

act.sa_sigaction=func;

sigaction(SIGUSR1, &act, &oact);

while (1)

{

printf("My pid=%d\n",getpid());

pause();

}

}

我们按下ctrl+c之后,就会显示信号值和发送信号的进程

四、自定义signal函数 利用sigaction函数的特性,定义自己的signal函数,使用起来与系统signal函数一样,但是功能比系统的signal函数更多 重启动版本 系统的signal函数原型太复杂,如下所示 void (*signal(int signo, void (*func)(int)))(int);  为了简化起见,我们定义了自己的Sigfunc类型,因此调用函数时只需要传入一个信号值即可

typedef void Sigfunc(int);

Sigfunc *signal(int signo, Sigfunc *func);

Sigfunc *signal(int signo, Sigfunc *func)

{

struct sigaction act, oact;

act.sa_handler = func;

sigemptyset(&act.sa_mask);

act.sa_flags = 0;

 

if (signo == SIGALRM){

#ifdef SA_INTERRUPT

act.sa_flags |= SA_INTERRUPT;

#endif

}

else {

#ifdef SA_RESTART

act.sa_flags |= SA_RESTART;

#denif

}

 

if (sigaction(signo, &act, &oact) < 0)

return(SIG_ERR);

return(oact.sa_handler);

}

函数的使用: 参数传入一个信号和一个信号处理函数指针返回值:在函数内部调用sigaction函数,并将相应信号的旧行为作为signal函数的返回值几点说明: 要用sigemptyset函数初始化act结构的sa_mask成员,不能简单的设置act.sa_mask=0如果信号是SIGALEM,我们就将sa_flags设置为SA_INTERRUPT,不希望重启动由SIGALRM信号中断的系统调用,原因是:我们希望为IO操作设置超时(可以参考)如果是其他信号,sa_flags标志就加入SA_RESTART,希望重启被中断的系统调用 不重新启动版本 如果想要系统调用被中断后不重新启动,则可以使用这一版本

typedef void Sigfunc(int);

Sigfunc *signal_intr(int signo, Sigfunc *func);

Sigfunc *signal_intr(int signo, Sigfunc *func)

{

struct sigaction act, oact;

act.sa_handler = func;

sigemptyset(&act.sa_mask);

act.sa_flags = 0;

 

#ifdef SA_INTERRUPT

act.sa_flags |= SA_INTERRUPT;

#endif

 

if (sigaction(signo, &act, &oact) < 0)

return(SIG_ERR);

return(oact.sa_handler);

}

如果系统定义了SA_INTERRUPT,那么为了提高可移植性。我们在sa_flags中增加该标志,这样就可以组织被中断的系统调用的重启动  五、siginfo_t枚举的si_value成员验证 siginfo_t的应用:有时我们接收到一个信号时,希望接受一些其他数据,就可以设置这个成员例如:我们有一个用链表实现一个简单的服务器,并且想要在规定的一个时间间隔发送一些数据。就可以定一个定时器,并且在接收到SIGALRM信号时,因为操作数据需要用到链表,将链表地址传递给union sigval的sival_ptr指针。这样当捕获SIGALRM信号时,就可以调用sa_action函数,然后通过参数2的sival_ptr获取这个指针

演示案例:

我们用一个进程给另外一个进程发送SIGINT信号,并且同时将一个整型100发送出去

//发送进程:向一个进程发送SIGINT信号,并且将整型100也传进去

#include

#include

#include

#include

 

int main(int argc,char *argv[])

{

if(argc!=2)

{

printf("arguments error!");

exit(0);

}

 

pid_t pid=atoi(argv[1]);//将进程号转化为整数

union sigval v;

v.sival_int=100; //发送整型100

 

sigqueue(pid,SIGINT,v);

return 0;

}

 

//此进程等待接受信号的传入,并有SIGINT的信号处理函数

#include

#include

#include

#include

 

void handler(int,siginfo_t *,void *);

 

int main(void)

{

struct sigaction act;

act.sa_sigaction=handler;

sigemptyset(&act.sa_mask);

act.sa_flags=SA_SIGINFO;

if(sigaction(SIGINT,&act,NULL)

//打印信息

printf("recv a sid=%d data=%d data=%d\n",sig,info->si_value.sival_int,info->si_int);

}

演示结果:



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3